/*
 * http://www.newsmth.net/bbscon.php?bid=335&id=325836
 */

#include <mutex>
#include <queue>

#include <iostream>
#include <thread>
#include <vector>
#include <algorithm>

template <typename T>
class concurrent_queue {
public:
	concurrent_queue( size_t max_size )
		: max_size_( max_size )
	{
	}

	T get()
	{
		std::lock_guard< std::mutex > lg( mutex_ );
		/*
		while( queue_.empty() ) {
			empty_cond_.wait( mutex_ );
		}
		*/
		empty_cond_.wait( mutex_, [&queue_](){ return !queue_.empty();} );
		T t = queue_.front();
		queue_.pop();
		full_cond_.notify_one();
		return t;
	}

	void put( const T &t )
	{
		std::lock_guard< std::mutex > lg( mutex_ );
		full_cond_.wait( mutex_, [&queue_, max_size_]() { return queue_.size() < max_size_; } );
		queue_.push( t );
		empty_cond_.notify_one();
		return;
	}

	bool full( void )
	{
		std::lock_guard<std::mutex> lg( mutex_ );
		return queue_.size() == max_size_;
	}

	bool empty( void )
	{
		std::lock_guard<std::mutex> lg( mutex_ );
		return queue_.empty();
	}

	size_t size( void )
	{
		std::lock_guard<std::mutex> lg( mutex_ );
		return queue_.size();
	}

private:
	size_t max_size_;
	std::queue< T > queue_;
	std::mutex mutex_;

	std::condition_variable_any empty_cond_;
	std::condition_variable_any full_cond_;
};

// testing ...
int main()
{
	concurrent_queue< int > cq(20);

	static const int producer_count = 4;
	std::vector< std::thread > producers;

	for( int i = 0; i < producer_count; i++ ) {
		producers.push_back( std::thread( [&cq]{
					for( int i = 0; i < 1000; i++ ) {
						cq.put( i );
						std::cout << "product: " << i <<  std::endl;
						sleep(1);
					}
				}) );
	}

	static const int consumer_count = 2;
	std::vector< std::thread > consumers;
	for( int i = 0; i < consumer_count; i++ ) {
		consumers.push_back( std::thread( [&cq]{
					for( int i = 0; i < 200000; i++ ) {
						std::cout << "consume: " <<  cq.get() << std::endl;
					}
				}) );
	}

	std::for_each( producers.begin(), producers.end(), [](std::thread &t) { t.join(); } );
	std::for_each( consumers.begin(), consumers.end(), [](std::thread &t) { t.join(); } );

	return 0;
}

